package server.controller;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import server.model.solver.maze.Maze;
import server.model.solver.maze.MazeDomain;
import server.model.solver.maze.MazeG;
import server.model.solver.maze.MazeH;
import server.model.solver.maze.algorithms.IAction;
import server.model.solver.maze.algorithms.a_star.AStar;
import server.model.solver.twoThousandFortyEight.Board;
import server.model.solver.twoThousandFortyEight.Direction;
import server.model.solver.twoThousandFortyEight.algorithms.minimax.AIsolver;
import common.DataTransfer;
import common.XmlSerializer;

/**
 * Server side class
 */
public class Server extends Thread {
	private ServerSettings settings;
	private ServerSocket server;
	private boolean disconnect;

	private ExecutorService threads;

	/**
	 * Server CTOR - starts all the needed params
	 */
	public Server() {
		System.out.println("Server Started");
		
		try {
			settings = (ServerSettings)XmlSerializer.loadSettings(".\\settings\\server.config", new ServerSettings());
			
			server = new ServerSocket(settings.getConnectionPort());
			server.setSoTimeout(settings.getSocketTimeout());
			threads = Executors.newFixedThreadPool(settings.getMaxNumberOfConnections());
			
			disconnect = false;
		}
		catch (IOException e) {
			System.out.println("Cannot find the \\settings\\server.config file.");
			disconnect();
		}
	}
	
	/**
	 * Disconnects the server
	 */
	public void disconnect() {
		disconnect = true;
		System.out.println("Server Disconnected");
	}
	
	ClientHandler clientHandler = new ClientHandler() {
		@Override
		public void handleClient(ObjectInputStream reader, ObjectOutputStream writer) {
			try {
				DataTransfer dataTransfer = (DataTransfer)reader.readObject();
				
				switch (dataTransfer.getGameName()) {
					case "Maze":
						writer.writeObject(getMazeSolution(dataTransfer.getBoard()));
						break;
					case "2048":
						writer.writeObject(get2048Solution(dataTransfer.getBoard(), dataTransfer.getScore()));
						break;
					default:
						break;
				}
				
				System.out.println("Client Disconnected");
			}
			catch (IOException | ClassNotFoundException | CloneNotSupportedException e) {
				e.printStackTrace();
			}
		}
	};
	
	/**
	 * Gets the solution for the maze game
	 * @param board the current state of the maze game board
	 * @return array list of solutions for the maze
	 */
	private ArrayList<Integer> getMazeSolution(int[][] board) {	
		Maze maze = new Maze(board);
		AStar as = new AStar(new MazeDomain(maze), new MazeG(), new MazeH());
		
		ArrayList<IAction> actions = as.search(maze.getStartState(), maze.getGoalState());
		
		ArrayList<Integer> gameSolution = new ArrayList<Integer>();
		
		for (IAction a : actions) 	
			gameSolution.add(a.getName());
		
		return gameSolution;
	}
	
	/**
	 * Gets the solution for the 2048 game
	 * @param board the current state of the 2048 game 
	 * @param score the current score of the 2048 game
	 * @return array of one step for the next move of 2048 game
	 * @throws CloneNotSupportedException
	 */
	private ArrayList<Integer> get2048Solution(int[][] board, int score) throws CloneNotSupportedException {
		Board gameBoard = new Board(board, score);
		Direction hint = AIsolver.findBestAlphaBetaMove(gameBoard, settings.getSolverDepth());
		
		ArrayList<Integer> gameSolution = new ArrayList<Integer>();
		
		if (hint == null) {
			gameSolution.add(0);
		}
		else {
			gameSolution.add(hint.getCode());
		}
		
		return gameSolution;
	}
	
	public void run() {
		try {
			while (!disconnect) {
				final Socket connection = server.accept();
				
				System.out.println("Client Connected");
				
				final ObjectOutputStream writer = new ObjectOutputStream(connection.getOutputStream());
				final ObjectInputStream reader = new ObjectInputStream(connection.getInputStream());

				threads.execute(new Runnable() {
					@Override
					public void run() {
						try {
							clientHandler.handleClient(reader, writer);

							reader.close();
							writer.close();
							connection.close();
						}
						catch(Exception e) {
							e.printStackTrace();
						}	
					}
				});
			}
		
			if (server != null) {
				server.close();
			}
		}
		catch (SocketTimeoutException e) {
			System.out.println("Server Timeout");
			disconnect();
			threads.shutdown();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}
